// // Copyright (c) 2009 All Right Reserved // // vl // // 2009-01-01 // Contains ... using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; using System.Text; using System.Xml.Serialization; using LargoCommon.Abstract; using LargoCommon.Interfaces; using LargoCommon.Midi; namespace LargoCommon.Music { /// /// Melodic Tone Collection. /// [Serializable] [XmlRoot] public sealed class MusicalToneCollection : Collection { #region Constructors /// /// Initializes a new instance of the MusicalToneCollection class. /// public MusicalToneCollection() { } /// /// Initializes a new instance of the MusicalToneCollection class. /// /// Given list. /// Set Ordinal Index. public MusicalToneCollection(IEnumerable givenList, bool setOrdinalIndex) { Contract.Requires(givenList != null); if (givenList == null) { return; } foreach (var tone in givenList) { this.AddTone(tone, setOrdinalIndex); } } /// /// Initializes a new instance of the MusicalToneCollection class. /// /// Given list. public MusicalToneCollection(IList givenList) : base(givenList) { } /// /// Initializes a new instance of the MusicalToneCollection class. /// /// Given tones. /// Rhythmical bit-range of the tones. /// Set Ordinal Index. [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1126:PrefixCallsCorrectly", Justification = "Reviewed.")] public MusicalToneCollection(IEnumerable givenTones, BitRange range, bool setOrdinalIndex) { if (givenTones == null) { return; } // ReSharper disable once LoopCanBePartlyConvertedToQuery foreach (var mtone in givenTones) { var toneRange = mtone.BitRange; //// (barNumber); if (toneRange == null) { continue; } if (range != null) { //// 2016/07 var interRange = toneRange.IntersectionWith(range); if (interRange == null || interRange.IsEmpty) { continue; } } if (mtone is MusicalTone tone) { this.AddTone(tone, setOrdinalIndex); } } } #endregion #region Properties /// /// Gets Tone Key. /// /// General property. public string ToneKey { get { var sb = new StringBuilder(); foreach (var s in this.Select(mt => MusicalProperties.GetNoteName(mt.Pitch.Element) + mt.Pitch.Octave)) { sb.Append(s); } return sb.ToString(); } } /// Gets list of all already defined tones. /// Property description. public MusicalToneCollection FormalTones { get { var elements = (from mt in this select mt.Pitch.Element).Distinct(); var firstTone = this.FirstOrDefault(); if (firstTone?.Pitch == null) { return null; } var harSystem = firstTone.Pitch.HarmonicSystem; var ftc = new MusicalToneCollection(); foreach (var p in elements.Select(element => new MusicalPitch(harSystem, 0, element))) { ftc.AddTone(new MusicalTone(p, firstTone.BitRange, firstTone.Loudness, firstTone.BarNumber), true); //// (firstTone.BarNumberFrom) } return ftc; } } /// Gets list of all already defined tones. /// Property description. public MusicalToneCollection ValidToneList { //// MusicalToneCollection get { //// 2011/11 added && mt.IsTrueTone var tones = from mt in this where !mt.IsEmpty && mt.IsTrueTone orderby mt.Pitch.SystemAltitude select mt; var collection = new MusicalToneCollection(tones, false); return collection; //// new MusicalToneCollection(list.ToList()); } } /// Gets String representation of the collection. /// Property description. [XmlAttribute] public string ToneSchema { get { var str = new StringBuilder(); byte i = 0; foreach (var s in from mt in this where mt != null && mt.IsTrueTone select mt.Pitch.ToString()) { str.Append(s); i++; if (i != this.Count) { str.Append(","); } } return str.ToString(); } } /// Gets For melodical tracks only. /// Property description. public MusicalOctave MeanOctave { get { const int limit = 100; var sumNotes = 0; var numNotes = 0; foreach (var mt in this.Where(mt => mt?.Pitch != null)) { sumNotes += mt.Pitch.MidiKeyNumber; numNotes++; if (numNotes >= limit) { break; } } var meanNote = numNotes != 0 ? (byte)((float)sumNotes / numNotes) : (byte)0; var firstTone = this.FirstOrDefault(); MusicalPitch mp; if (firstTone?.Pitch != null) { var harSystem = firstTone.Pitch.HarmonicSystem; mp = harSystem.GetPitch(meanNote); } else { mp = new MusicalPitch(meanNote); } return (mp != null) ? (MusicalOctave)mp.Octave : MusicalOctave.None; } } /// Gets BandType. /// Property description. public MusicalBand MeanBandType { get { const int limit = 100; var sumNotes = 0; var numNotes = 0; foreach (var mt in this.Where(mt => mt?.Pitch != null)) { sumNotes += mt.Pitch.MidiKeyNumber; numNotes++; if (numNotes >= limit) { break; } } var meanNote = numNotes != 0 ? (byte)((float)sumNotes / numNotes) : (byte)0; return MusicalProperties.BandOfPitch(meanNote); } } /// Gets Mean Duration. /// Property description. public float MeanDuration { get { const int limit = 100; float sumDur = 0; var numNotes = 0; foreach (var mt in this.Where(mt => mt != null && !mt.IsEmpty && mt.RhythmicOrder != 0)) { sumDur += (float)mt.Duration / mt.RhythmicOrder; numNotes++; if (numNotes >= limit) { break; } } var meanDur = numNotes != 0 ? sumDur / numNotes : 0; return meanDur; } } /// Gets Mean Loudness. /// Property description. public MusicalLoudness MeanLoudness { get { const int limit = 100; float sumLoudness = 0; var numNotes = 0; foreach (var mt in this.Where(mt => mt != null && mt.Loudness > 0)) { sumLoudness += (short)mt.Loudness; numNotes++; if (numNotes >= limit) { break; } } var meanLoudness = numNotes != 0 ? sumLoudness / numNotes : 0; return (MusicalLoudness)(byte)Math.Round(meanLoudness); } } /// /// Gets a value indicating whether Contains Halftone. /// /// Property description. public bool ContainsHalftone { get { for (var i = 1; i < this.Count; i++) { var t0 = this.ElementAt(i - 1); var t1 = this.ElementAt(i); //// Sequences containing halftones are considered to be melodic if (t0 == null || t1 == null || t0.Pitch == null || t1.Pitch == null) { continue; } if (Math.Abs(t0.Pitch.SystemAltitude - t1.Pitch.SystemAltitude) == 1) { return true; //// Avoid multiple or conditional return statements. } } return false; } } /// /// Gets a value indicating whether is is a simple stagnation. /// /// Property description. public bool IsSimpleStagnation { get { if (this.Count != 2) { return false; } var t0 = this.ElementAt(0); var t1 = this.ElementAt(1); if (t0 == null || t1 == null || t0.Pitch == null || t1.Pitch == null) { return false; } return t0.Pitch.SystemAltitude == t1.Pitch.SystemAltitude; } } #endregion #region Static support /// /// Guess Mel Part Type. /// /// Band type. /// Melodic Motion Allowed. /// Returns value. [JetBrains.Annotations.PureAttribute] public static MelodicFunction GuessMelodicType(MusicalBand bandType, bool melMotionAllowed) { MelodicFunction mpt; switch (bandType) { case MusicalBand.HighTones: { mpt = melMotionAllowed ? MelodicFunction.MelodicMotion : MelodicFunction.HarmonicMotion; break; } case MusicalBand.MiddleTones: { mpt = MelodicFunction.HarmonicMotion; //// HarmonicFilling; break; } case MusicalBand.BassTones: { mpt = MelodicFunction.HarmonicBass; break; } default: { mpt = MelodicFunction.HarmonicFilling; break; } } return mpt; } /// /// Harmonic Mel Part Type. /// /// Band type. /// Returns value. [JetBrains.Annotations.PureAttribute] public static MelodicFunction HarmonicMelodicType(MusicalBand bandType) { var mpt = MelodicFunction.None; switch (bandType) { case MusicalBand.HighTones: { mpt = MelodicFunction.HarmonicMotion; break; } case MusicalBand.MiddleTones: { mpt = MelodicFunction.HarmonicFilling; break; } case MusicalBand.BassTones: { mpt = MelodicFunction.HarmonicBass; break; } case MusicalBand.Any: break; case MusicalBand.BassBeat: break; case MusicalBand.MiddleBeat: break; case MusicalBand.HighBeat: break; //// resharper default: break; } return mpt; } #endregion #region Public methods /// Add on musical tone to the end of part. /// Musical tone. /// Set Ordinal Index. public void AddTone(MusicalTone givenTone, bool setOrdinalIndex) { Contract.Requires(givenTone != null); //// if (givenTone == null) { return false; } if (setOrdinalIndex) { givenTone.OrdinalIndex = this.Count; } this.Add(givenTone); } /// /// Add Collection. /// /// Given tones. /// Set Ordinal Index. public void AddCollection(MusicalToneCollection givenTones, bool setOrdinalIndex) { Contract.Requires(givenTones != null); //// if (givenTone == null) { return false; } givenTones.ForAll(musicalTone => this.AddTone(musicalTone, setOrdinalIndex)); } /// /// Has Any Inconsistency With Harmony. /// /// Harmonic Structure. /// Harmonic Range. /// Returns value. public bool HasAnyInconsistencyWithHarmony(BinaryStructure harmonicStructure, BitRange harmonicRange) { if (harmonicStructure == null || harmonicRange == null) { return false; } return this.Where(mt => mt != null && harmonicRange.CoverRange(mt.BitRange)).Any(mt => (mt.Pitch != null) //// (mt.BarNumberFrom) && !harmonicStructure.IsOn(mt.Pitch.Element)); } /// /// Rhythmic Pattern. /// /// Returns value. public string RhythmicPattern() { var sb = new StringBuilder(); foreach (var mt in this.Where(mt => mt != null)) { sb.Append(mt.Duration.ToString(CultureInfo.CurrentCulture.NumberFormat)); sb.Append(" "); } return sb.ToString(); } /// /// Determine Harmonic Struct. /// /// Harmonic modality. /// Returns value. [JetBrains.Annotations.PureAttribute] public HarmonicStructure DetermineHarmonicStruct(BinarySchema harmonicModality) { Contract.Requires(harmonicModality != null); //// if (harmonicModality == null) { return null; } if (this.Count == 0) { return null; } var harSystem = HarmonicSystem.GetHarmonicSystem(DefaultValue.HarmonicOrder); var hstruct = new HarmonicStructure(harSystem, (string)null) { ToneSchema = null }; var toneValue = this.DetermineToneValues(harSystem, harmonicModality); SortToneValues(hstruct, toneValue); hstruct.DetermineLevel(); hstruct.DetermineBehavior(); //// string s = hstruct.ToneSchema; return hstruct; } /// /// Export To Midi Events. /// /// Event Collection. /// Midi Instrument. /// Bar division. public void ExportToMidiEvents(MidiEventCollection events, byte instrument, int barDivision) { Contract.Requires(events != null); if (events == null) { return; } //// if (channel != MidiChannel.DrumChannel) { events.PutInstrument(0, instrument); //// } foreach (var mt in this.Where(mt => mt != null && mt.RhythmicOrder > 0)) { //// RealTone rt = new RealTone(mt, instrument, channel); mt.InstrumentNumber = instrument; //// mt.Channel = channel; mt.WriteTo(events, barDivision); } } #endregion #region String representation /// String representation of the object. /// Returns value. public override string ToString() { var s = new StringBuilder(); this.ForAll(musicalTone => s.Append(musicalTone)); return s.ToString(); } #endregion #region Private static methods /// /// Sorts the tone values. /// /// The harmonic structure. /// The tone value. private static void SortToneValues(HarmonicStructure hstruct, Dictionary toneValue) { Contract.Requires(toneValue != null); if (hstruct == null) { return; } //// Sorts toneValues by weight var values = toneValue.Values; var top = (from v in values select v).OrderByDescending(v => v).Take(3).ToList(); var limit = 3; //// max number of tones in the structure foreach (var kvp in toneValue) { if (top.Contains(kvp.Value)) { var elem = (byte)kvp.Key; // % harSystem.Order hstruct.On(elem); limit--; } if (limit == 0) { break; } } } #endregion #region Private methods /// /// Determines the tone values. /// /// The harmonic system. /// The harmonic modality. /// Returns value. private Dictionary DetermineToneValues(HarmonicSystem harSystem, BinarySchema harmonicModality) { Contract.Requires(harmonicModality != null); if (harSystem == null) { return null; } const float modalToneIncrease = 2.0f; const float startingToneIncrease = 0.1f; var toneValue = new Dictionary(); //// Assign weight value to any modalAltitude for (byte level = 0; level < harmonicModality.Level; level++) { int modalAltitude = harmonicModality.PlaceAtLevel(level); float value = 0; foreach (var mt in this) { if (mt != null && mt.IsTrueTone && harSystem.Order != 0) { var formalAltitude = mt.Pitch.SystemAltitude % harSystem.Order; if (formalAltitude == modalAltitude) { value += modalToneIncrease; //// 0.5 //// Long tones have higher values //// value += mt.Duration - (mt.Pitch.Octave * 0.1f); //// Starting tones have higher values if (mt.BitFrom == 0) { value += startingToneIncrease; } } //// The tones consonant with other tones have higher values Contract.Assume(formalAltitude - modalAltitude > short.MinValue); var formalDistance = formalAltitude - modalAltitude; var v = MusicalInterval.GuessSonanceValue(formalDistance); value += v; } if (toneValue.ContainsKey(modalAltitude)) { toneValue[modalAltitude] += value; } else { toneValue.Add(modalAltitude, value); } } } return toneValue; } #endregion } }